/**
 * GPoint class
 * 点 类
 */
export class GPoint {
	Point: cc.Vec2;
	ID: number;
	Angle: number;

	get X() {
		return this.Point.x;
	}
	get Y() {
		return this.Point.y;
	}

	constructor(point: cc.Vec2, id = 0, angle = 0.0) {
		this.Point = point || cc.v2();
		this.ID = id;
		this.Angle = angle; // normalized turning angle, 归一化角度
	}
}

/**
 * GPointCloud class: a point-cloud template
 * GPointCloud类:点云模板
 */
export class GPointCloud {
	Name: string = "";
	Points: GPoint[] = null;
	constructor(name: string, points: GPoint[]) {
		this.Name = name;
		this.Points = Resample(points, NumPoints);
		this.Points = Scale(this.Points);
		this.Points = TranslateTo(this.Points, Origin);
		this.Points = ComputeNormalizedTurningAngles(this.Points);
	}
}

/**
 * GResult class
 * 结果类
 */
export class GResult {
	Name: string = "";
	Score: number = 0;
	Time: number = 0;
	constructor(name: string, score: number, ms: number) {
		this.Name = name;
		this.Score = score;
		this.Time = ms;
	}
}

// Gestures constants
// 手势常量
const NumPoints = 32;
const Origin = new GPoint(cc.v2(), 0, 0);

/**
 * Gestures Recognizer class
 * 手势 识别器/类
 */
export class Gestures {

	/**
	 * The Point-Cloud Recognizer API begins here -- 3 methods: Recognize(), AddGesture(), DeleteUserGestures()
	 * 点云识别器API从这里开始——3个方法:Recognize()， AddGesture()， DeleteUserGestures()
	 * @param GpointClouds 
	 */
	constructor(GpointClouds?: GPointCloud[]) {
		Array.isArray(GpointClouds) && this.Update(GpointClouds);
	}

	/**
	 * one predefined point-cloud for each gesture
	 * 每个手势都有一个预定义的点云
	 */
	public get GPointClouds(): GPointCloud[] {
		return this.gPointClouds;
	}
	private set GPointClouds(value: GPointCloud[]) {
		this.gPointClouds = value;
	}
	private gPointClouds: GPointCloud[] = new Array();
	NumGPointClouds: number = 0;
	/**
	 * 更新手势列表
	 * @param GpointClouds 
	 */
	public Update(GpointClouds: GPointCloud[]) {
		this.NumGPointClouds = GpointClouds ? GpointClouds.length : 0;
		for (let i = 0; i < GpointClouds.length; i++) {
			const pc = GpointClouds[i];
			let points = [];
			pc.Points.forEach(value => { points.push(new GPoint(cc.v2(value.Point.x, value.Point.y), value.ID, value.Angle)) });
			this.AddGesture(pc.Name, points);
		}
	}
	/**
	 * 识别
	 * @param {GPoint[]} points 
	 * @returns 
	 */
	public Recognize(points: GPoint[]): GResult {
		var t0 = Date.now();
		var candidate = new GPointCloud("", points);
		var u = -1;
		var b = +Infinity;
		for (var i = 0; i < this.GPointClouds.length; i++) // for each point-cloud template 遍历每个点云模板
		{
			var d = Math.min(
				CloudDistance(candidate.Points, this.GPointClouds[i].Points),
				CloudDistance(this.GPointClouds[i].Points, candidate.Points)
			);
			if (d < b) {
				b = d; // best (least) distance 最佳(最小)距离
				u = i; // point-cloud index 点云索引
			}
		}
		var t1 = Date.now();
		return (u == -1) ? new GResult("No match.", 0.0, t1 - t0) : new GResult(this.GPointClouds[u].Name, b > 1.0 ? 1.0 / b : 1.0, t1 - t0);
	}
	/**
	 * 添加手势
	 * @param name 
	 * @param points 
	 * @returns {number} total 返回同样名字的手势数量
	 */
	public AddGesture(name: string, points: GPoint[]): number {
		this.GPointClouds[this.GPointClouds.length] = new GPointCloud(name, points);
		var total = 0;
		for (var i = 0; i < this.GPointClouds.length; i++) {
			if (this.GPointClouds[i].Name == name)
				total++;
		}
		return total;
	}
	/**
	 * clears any beyond the original set
	 * 清除原始集合之外的任何内容
	 * @returns 剩余多少默认的 count
	 */
	public DeleteUserGestures(): number {
		this.GPointClouds.length = this.NumGPointClouds;
		return this.NumGPointClouds;
	}

}

//#region Private helper functions from here on down
function CloudDistance(pts1: GPoint[], pts2: GPoint[]) {
	var matched = new Array(pts1.length); // pts1.length == pts2.length
	for (var k = 0; k < pts1.length; k++)
		matched[k] = false;
	var sum = 0;
	for (var i = 0; i < pts1.length; i++) {
		var index = -1;
		var min = +Infinity;
		for (var j = 0; j < pts1.length; j++) {
			var d = DistanceWithAngle(pts1[i], pts2[j]);
			if (d < min) {
				min = d;
				index = j;
			}
		}
		matched[index] = true;
		sum += min;
	}
	for (var j = 0; j < matched.length; j++) {
		if (!matched[j]) {
			var min = +Infinity;
			for (var i = 0; i < pts1.length; i++) {
				var d = DistanceWithAngle(pts1[i], pts2[j]);
				if (d < min)
					min = d;
			}
			sum += min;
		}
	}
	return sum;
}
function Resample(points: GPoint[], n: number) {
	var I = PathLength(points) / (n - 1); // interval length 间隔长度
	var D = 0.0;
	var newpoints = new Array(points[0]);
	for (var i = 1; i < points.length; i++) {
		if (points[i].ID == points[i - 1].ID) {
			var d = Distance(points[i - 1], points[i]);
			if ((D + d) >= I) {
				var qx = points[i - 1].X + ((I - D) / d) * (points[i].X - points[i - 1].X);
				var qy = points[i - 1].Y + ((I - D) / d) * (points[i].Y - points[i - 1].Y);
				var q = new GPoint(cc.v2(qx, qy), points[i].ID);
				newpoints[newpoints.length] = q; // append new point 'q'
				points.splice(i, 0, q); // insert 'q' at position i in points s.t. 'q' will be the next i  在点s.t的位置I插入'q'。'q'将是下一个I
				D = 0.0;
			}
			else D += d;
		}
	}
	// sometimes we fall a rounding-error short of adding the last point, so add it if so 有时，我们在加上最后一个点时会出现舍入误差，如果是，就加上它
	if (newpoints.length == n - 1)
		newpoints[newpoints.length] = new GPoint(cc.v2(points[points.length - 1].X, points[points.length - 1].Y), points[points.length - 1].ID);
	return newpoints;
}
function Scale(points: GPoint[]) {
	var minX = +Infinity, maxX = -Infinity, minY = +Infinity, maxY = -Infinity;
	for (var i = 0; i < points.length; i++) {
		let point = points[i].Point;
		minX = Math.min(minX, point.x);
		minY = Math.min(minY, point.y);
		maxX = Math.max(maxX, point.x);
		maxY = Math.max(maxY, point.y);
	}
	var size = Math.max(maxX - minX, maxY - minY);
	var newpoints = new Array();
	for (var i = 0; i < points.length; i++) {
		var point = cc.v2((points[i].X - minX) / size, (points[i].Y - minY) / size);
		newpoints[newpoints.length] = new GPoint(point, points[i].ID);
	}
	return newpoints;
}
function TranslateTo(points: GPoint[], pt: GPoint) // translates points' centroid
{
	var c = Centroid(points);
	var newpoints = new Array();
	for (var i = 0; i < points.length; i++) {
		var qx = points[i].X + pt.X - c.X;
		var qy = points[i].Y + pt.Y - c.Y;
		newpoints[newpoints.length] = new GPoint(cc.v2(qx, qy), points[i].ID);
	}
	return newpoints;
}
function ComputeNormalizedTurningAngles(points: GPoint[]) {
	var newpoints = new Array();
	// first point
	newpoints[0] = new GPoint(cc.v2(points[0].X, points[0].Y), points[0].ID);
	for (var i = 1; i < points.length - 1; i++) {
		var dx = (points[i + 1].X - points[i].X) * (points[i].X - points[i - 1].X);
		var dy = (points[i + 1].Y - points[i].Y) * (points[i].Y - points[i - 1].Y);
		var dn = Distance(points[i + 1], points[i]) * Distance(points[i], points[i - 1]);
		var cosangle = Math.max(-1.0, Math.min(1.0, (dx + dy) / dn)); // ensure [-1,+1]
		var angle = Math.acos(cosangle) / Math.PI; // normalized angle
		newpoints[newpoints.length] = new GPoint(cc.v2(points[i].X, points[i].Y), points[i].ID, angle);
	}
	// last point
	newpoints[newpoints.length] = new GPoint(cc.v2(points[points.length - 1].X, points[points.length - 1].Y), points[points.length - 1].ID);
	return newpoints;
}
//中心id
function Centroid(points: GPoint[]) {
	var point = cc.v2();
	for (var i = 0; i < points.length; i++) {
		point.x += points[i].X;
		point.y += points[i].Y;
	}
	point.x /= points.length;
	point.y /= points.length;
	return new GPoint(point, 0);
}
/**
 * length traversed by a point path 
 * 点路径遍历的长度
 * @param points 
 * @returns 
 */
function PathLength(points: GPoint[]) {
	var d = 0.0;
	for (var i = 1; i < points.length; i++) {
		if (points[i].ID == points[i - 1].ID)
			d += Distance(points[i - 1], points[i]);
	}
	return d;
}
function DistanceWithAngle(p1: GPoint, p2: GPoint) {
	var dx = p2.X - p1.X;
	var dy = p2.Y - p1.Y;
	var da = p2.Angle - p1.Angle;
	return Math.sqrt(dx * dx + dy * dy + da * da);
}
/**
 *  Euclidean distance between two points 
 * 	两点之间的欧氏距离
 */
function Distance(p1: GPoint, p2: GPoint) {
	var dx = p2.X - p1.X;
	var dy = p2.Y - p1.Y;
	return Math.sqrt(dx * dx + dy * dy);
}
//#endregion
